package com.songshuang.myutilsboot.stlf.core;

import com.songshuang.myutilsboot.stlf.model.Models;
import com.songshuang.myutilsboot.stlf.util.LogWriter;

import java.util.ArrayList;
import java.util.List;

/**
 * ANN预测器，实现神经网络功率预测算法
 * 对应C++中的annpv_his()和annpv_for()函数
 */
public final class AnnPredictor {
    private AnnPredictor() {}

    /**
     * 执行ANN预测计算
     * @param ip 输入参数
     * @param fi 预测结果信息
     */
    public static boolean predict(Models.InputParameter ip, Models.ForecastInfo fi) {
        if (ip.global_rc == null) {
            return false;
        }

        // 初始化神经网络参数
        AnnConfig config = new AnnConfig(ip.global_rc);
        // 若传入了model_version且ann_flag!=1，尝试从模型文件读取权重
        boolean needReadModel = ip.global_rc.pv_model_version != null && ip.global_rc.ann_flag != 1;
        if (needReadModel) {
            try {
                ModelIO.readModel(ip.global_rc.pv_model_version, config);
            } catch (Exception e) {
                LogWriter.write("读取模型文件失败", e.getMessage());
                return false;
            }
        }

        // 若 ann_flag==1，执行简单BP训练直到 anntol 或迭代上限（对齐C++ ann_bpfun 的核心）
        if (ip.global_rc.ann_flag == 1 && ip.his_data != null && !ip.his_data.isEmpty()) {
            trainBp(ip, config);
            // 训练后将模型写入指定版本
            if (ip.global_rc.pv_model_version != null) {
                try {
                    ModelIO.writeModel(ip.global_rc.pv_model_version, config);
                } catch (Exception e) {
                    LogWriter.write("写入模型文件失败", e.getMessage());
                }
            }
        }
        
        int sdnum = (int) (ip.global_rc.sdnum > 0 ? ip.global_rc.sdnum : 96);

        // 处理历史数据预测（按日期分组并补齐 1..sdnum）
        if (ip.his_data != null && !ip.his_data.isEmpty()) {
            fi.His_Report = predictHistoricalData(ip.his_data, config, sdnum);
        }

        // 处理预测数据（按日期分组并补齐 1..sdnum）
        if (ip.for_data != null && !ip.for_data.isEmpty()) {
            fi.Fore_Report = predictForecastData(ip.for_data, config, sdnum);
        }
        return true;
    }

    // 简化版BP训练，核心等式对齐：sigmoid，dyErr= (y-y0)*y*(1-y)，dhErr= (V*dyErr)*h*(1-h)
    private static void trainBp(Models.InputParameter ip, AnnConfig cfg) {
        final int sdnum = (int) (ip.global_rc.sdnum > 0 ? ip.global_rc.sdnum : 96);
        final double anntol = ip.global_rc.anntol > 0 ? ip.global_rc.anntol : 0.02;
        final int MAX_ITR = 2000;
        final double ANN_W = 0.30;
        final double ANN_V = 0.30;
        final double ANN_C = 0.10; // 动量

        double[][] dW0 = new double[cfg.xnum + 1][cfg.hnum + 1];
        double[][] dV0 = new double[cfg.hnum + 1][cfg.ynum + 1];

        // 构造按天到时段的历史样本
        java.util.Map<String, java.util.Map<Long, Models.HisData>> byDate = new java.util.LinkedHashMap<>();
        for (Models.HisData d : ip.his_data) byDate.computeIfAbsent(d.Date, k -> new java.util.HashMap<>()).put(d.Sd, d);

        java.util.List<double[]> X = new java.util.ArrayList<>();
        java.util.List<double[]> Y = new java.util.ArrayList<>();
        for (java.util.Map.Entry<String, java.util.Map<Long, Models.HisData>> e : byDate.entrySet()) {
            String date = e.getKey();
            java.util.Map<Long, Models.HisData> sdMap = e.getValue();
            for (int sd = 1; sd <= sdnum; sd++) {
                Models.HisData d = sdMap.getOrDefault((long) sd, defaultHis(date, sd));
                double[] x = buildInputFeatures(d, cfg, sdnum);
                double[] y0 = new double[cfg.ynum + 1];
                y0[1] = d.MW0 / 100.0;
                X.add(x);
                Y.add(y0);
            }
        }

        double[] yhErr = new double[X.size() + 1];
        int itr = 0;
        java.util.Random rnd = new java.util.Random(123);
        while (itr < MAX_ITR) {
            int sampleCount = X.size();
            for (int ii = 0; ii < sampleCount; ii++) {
                double[] x = X.get(ii);
                double[] y0 = Y.get(ii);

                // forward
                double[] h = new double[cfg.hnum + 1];
                for (int j = 1; j <= cfg.hnum; j++) {
                    double sum = 0;
                    for (int i = 1; i <= cfg.xnum; i++) sum += cfg.W[i][j] * x[i];
                    h[j] = MathUtil.sigmoid(sum - cfg.hth[j]);
                }
                double[] y = new double[cfg.ynum + 1];
                for (int j = 1; j <= cfg.ynum; j++) {
                    double sum = 0;
                    for (int i = 1; i <= cfg.hnum; i++) sum += cfg.V[i][j] * h[i];
                    y[j] = MathUtil.sigmoid(sum - cfg.yth[j]);
                }

                // errors
                double[] dyErr = new double[cfg.ynum + 1];
                for (int j = 1; j <= cfg.ynum; j++) {
                    double dy = y[j] - y0[j];
                    dyErr[j] = dy * y[j] * (1 - y[j]);
                }
                double[] dhErr = new double[cfg.hnum + 1];
                for (int i = 1; i <= cfg.hnum; i++) {
                    double sum = 0;
                    for (int j = 1; j <= cfg.ynum; j++) sum += cfg.V[i][j] * dyErr[j];
                    dhErr[i] = sum * h[i] * (1 - h[i]);
                }

                // gradients with momentum
                double[][] dW = new double[cfg.xnum + 1][cfg.hnum + 1];
                double[][] dV = new double[cfg.hnum + 1][cfg.ynum + 1];
                double[] dhth = new double[cfg.hnum + 1];
                double[] dyth = new double[cfg.ynum + 1];

                for (int j = 1; j <= cfg.hnum; j++) {
                    for (int i = 1; i <= cfg.xnum; i++) {
                        dW[i][j] = ANN_W * dhErr[j] * x[i] + (itr == 0 ? 0 : ANN_C * dW0[i][j]);
                    }
                    dhth[j] = ANN_W * dhErr[j];
                }
                for (int j = 1; j <= cfg.ynum; j++) {
                    for (int i = 1; i <= cfg.hnum; i++) {
                        dV[i][j] = ANN_V * dyErr[j] * h[i] + (itr == 0 ? 0 : ANN_C * dV0[i][j]);
                    }
                    dyth[j] = ANN_V * dyErr[j];
                }

                // apply updates
                for (int i = 1; i <= cfg.xnum; i++) {
                    for (int j = 1; j <= cfg.hnum; j++) cfg.W[i][j] -= dW[i][j];
                }
                for (int i = 1; i <= cfg.hnum; i++) {
                    for (int j = 1; j <= cfg.ynum; j++) cfg.V[i][j] -= dV[i][j];
                }
                for (int j = 1; j <= cfg.hnum; j++) cfg.hth[j] += dhth[j];
                for (int j = 1; j <= cfg.ynum; j++) cfg.yth[j] += dyth[j];

                dW0 = dW;
                dV0 = dV;
            }

            // compute max error
            double maxErr = 0;
            for (int ii2 = 0; ii2 < X.size(); ii2++) {
                double[] x = X.get(ii2);
                double[] y0 = Y.get(ii2);
                double[] h = new double[cfg.hnum + 1];
                for (int j = 1; j <= cfg.hnum; j++) {
                    double sum = 0;
                    for (int i = 1; i <= cfg.xnum; i++) sum += cfg.W[i][j] * x[i];
                    h[j] = MathUtil.sigmoid(sum - cfg.hth[j]);
                }
                double[] y = new double[cfg.ynum + 1];
                for (int j = 1; j <= cfg.ynum; j++) {
                    double sum = 0;
                    for (int i = 1; i <= cfg.hnum; i++) sum += cfg.V[i][j] * h[i];
                    y[j] = MathUtil.sigmoid(sum - cfg.yth[j]);
                }
                double err = Math.abs(y[1] - y0[1]);
                if (err > maxErr) maxErr = err;
            }
            itr++;
            if (ip.global_rc.debug_flag > 0) {
                if (itr <= 10 || itr % 100 == 0) {
                    com.songshuang.myutilsboot.stlf.util.LogWriter.write("模型训练过程输出信息:", "itr=" + itr + "\tmaxErr=" + maxErr);
                }
            }
            if (maxErr <= anntol) break;
        }
    }

    /**
     * 预测历史数据，对应C++中的annpv_his()函数
     */
    private static List<Models.SigSdInf> predictHistoricalData(List<Models.HisData> hisDataList, AnnConfig config, int sdnum) {
        List<Models.SigSdInf> results = new ArrayList<>();
        // 按日期分组
        java.util.Map<String, java.util.Map<Long, Models.HisData>> byDate = new java.util.LinkedHashMap<>();
        for (Models.HisData d : hisDataList) {
            byDate.computeIfAbsent(d.Date, k -> new java.util.HashMap<>()).put(d.Sd, d);
        }
        for (java.util.Map.Entry<String, java.util.Map<Long, Models.HisData>> e : byDate.entrySet()) {
            String date = e.getKey();
            java.util.Map<Long, Models.HisData> sdMap = e.getValue();
            for (int sd = 1; sd <= sdnum; sd++) {
                Models.HisData data = sdMap.getOrDefault((long) sd, defaultHis(date, sd));
                double[] inputFeatures = buildInputFeatures(data, config, sdnum);
                double predictedPower = forwardPropagation(inputFeatures, config);
                if (predictedPower < config.pwrtol) predictedPower = 0;
                Models.SigSdInf result = new Models.SigSdInf();
                result.Date = date;
                result.Sd = sd;
                result.MW0 = data.MW0;
                result.MW = predictedPower;
                result.Err = predictedPower - data.MW0;
                result.Pct = (data.MW0 != 0) ? (result.Err / data.MW0) * 100 : 0;
                results.add(result);
            }
        }
        return results;
    }

    /**
     * 预测未来数据，对应C++中的annpv_for()函数
     */
    private static List<Models.SigSdInf> predictForecastData(List<Models.ForData> forDataList, AnnConfig config, int sdnum) {
        List<Models.SigSdInf> results = new ArrayList<>();
        // 按日期分组
        java.util.Map<String, java.util.Map<Long, Models.ForData>> byDate = new java.util.LinkedHashMap<>();
        for (Models.ForData d : forDataList) {
            byDate.computeIfAbsent(d.Date, k -> new java.util.HashMap<>()).put(d.Sd, d);
        }
        for (java.util.Map.Entry<String, java.util.Map<Long, Models.ForData>> e : byDate.entrySet()) {
            String date = e.getKey();
            java.util.Map<Long, Models.ForData> sdMap = e.getValue();
            for (int sd = 1; sd <= sdnum; sd++) {
                Models.ForData data = sdMap.getOrDefault((long) sd, defaultFor(date, sd));
                double[] inputFeatures = buildInputFeatures(data, config, sdnum);
                double predictedPower = forwardPropagation(inputFeatures, config);
                if (predictedPower < config.pwrtol) predictedPower = 0;
                Models.SigSdInf result = new Models.SigSdInf();
                result.Date = date;
                result.Sd = sd;
                result.MW0 = 0;
                result.MW = predictedPower;
                result.Err = 0;
                result.Pct = 0;
                results.add(result);
            }
        }
        return results;
    }

    /**
     * 构建输入特征向量，对应C++中的特征提取逻辑
     */
    private static double[] buildInputFeatures(Models.HisData data, AnnConfig config, int sdnum) {
        double[] features = new double[config.xnum + 1]; // +1 for 1-based indexing
        int index = 1; // 从1开始，对应C++的索引风格
        
        for (String factor : config.infacts) {
            switch (factor) {
                case "sunalt":
                    features[index] = calculateSunAltitude(data.Date, data.Sd, config.pv_nlat, sdnum) / 100.0;
                    index++;
                    break;
                case "wea_Temp":
                    features[index] = data.Temp / 100.0;
                    index++;
                    break;
                case "wea_windSpeed":
                    features[index] = data.windSpeed / 100.0;
                    index++;
                    break;
                case "wea_windDir":
                    features[index] = data.windDir / 100.0;
                    index++;
                    break;
                case "wea_Pa":
                    features[index] = data.Pa / 100.0;
                    index++;
                    break;
                case "wea_Transp":
                    features[index] = data.Transp / 100.0;
                    index++;
                    break;
                case "wea_Humid":
                    features[index] = data.Humid / 100.0;
                    index++;
                    break;
                case "wea_Cloud":
                    features[index] = data.Cloud / 100.0;
                    index++;
                    break;
                case "wea_Dni":
                    features[index] = data.Dni / 100.0;
                    index++;
                    break;
            }
        }
        
        return features;
    }

    /**
     * 构建输入特征向量，对应C++中的特征提取逻辑（ForData版本）
     */
    private static double[] buildInputFeatures(Models.ForData data, AnnConfig config, int sdnum) {
        double[] features = new double[config.xnum + 1]; // +1 for 1-based indexing
        int index = 1; // 从1开始，对应C++的索引风格
        
        for (String factor : config.infacts) {
            switch (factor) {
                case "sunalt":
                    features[index] = calculateSunAltitude(data.Date, data.Sd, config.pv_nlat, sdnum) / 100.0;
                    index++;
                    break;
                case "wea_Temp":
                    features[index] = data.Temp / 100.0;
                    index++;
                    break;
                case "wea_windSpeed":
                    features[index] = data.windSpeed / 100.0;
                    index++;
                    break;
                case "wea_windDir":
                    features[index] = data.windDir / 100.0;
                    index++;
                    break;
                case "wea_Pa":
                    features[index] = data.Pa / 100.0;
                    index++;
                    break;
                case "wea_Transp":
                    features[index] = data.Transp / 100.0;
                    index++;
                    break;
                case "wea_Humid":
                    features[index] = data.Humid / 100.0;
                    index++;
                    break;
                case "wea_Cloud":
                    features[index] = data.Cloud / 100.0;
                    index++;
                    break;
                case "wea_Dni":
                    features[index] = data.Dni / 100.0;
                    index++;
                    break;
            }
        }
        
        return features;
    }

    /**
     * 神经网络前向传播，对应C++中的hxfun()和yhfun()函数
     */
    private static double forwardPropagation(double[] x, AnnConfig config) {
        // 隐藏层计算 hxfun()
        double[] h = new double[config.hnum + 1];
        for (int j = 1; j <= config.hnum; j++) {
            double sum = 0;
            for (int i = 1; i <= config.xnum; i++) {
                sum += config.W[i][j] * x[i];
            }
            h[j] = MathUtil.sigmoid(sum - config.hth[j]);
        }
        
        // 输出层计算 yhfun()
        double[] y = new double[config.ynum + 1];
        for (int j = 1; j <= config.ynum; j++) {
            double sum = 0;
            for (int i = 1; i <= config.hnum; i++) {
                sum += config.V[i][j] * h[i];
            }
            y[j] = MathUtil.sigmoid(sum - config.yth[j]);
        }
        
        return y[1] * 100; // 对应C++中的 y[1]*100
    }

    /**
     * 计算太阳高度角，对应C++中的sunalt_fun()函数
     * 简化实现，实际应该根据日期和时间精确计算
     */
    private static double calculateSunAltitude(String date, long sd, double latitude, int sdnum) {
        // 对齐C++：sunalt_fun(date_str,time_str)
        // 1) 由sd推导时间字符串 time_str，sd=1..sdnum，每段=24h/sdnum
        String time = sdToTimeString(sd, sdnum);
        // 2) 计算 thet（按年内日角）与 delt（赤纬）
        double thet = thetFun(date);
        double deltDeg = deltFun(thet);
        double delt = Math.toRadians(deltDeg);
        double phi = Math.toRadians(latitude);
        double t = timeToSeconds(time);
        double ta0 = (t / 3600.0 - 12.0) * 15.0;
        double ta = Math.toRadians(ta0);
        double sinh = Math.sin(phi) * Math.sin(delt) + Math.cos(phi) * Math.cos(delt) * Math.cos(ta);
        double ret = Math.toDegrees(Math.asin(sinh));
        return Math.max(0, ret);
    }

    private static String sdToTimeString(long sd, int sdnum) {
        // 等价 C++ 的 sdfun，按 sdnum 均分 24 小时，sd 从 1 开始
        double secondsPerSlot = 24 * 3600.0 / sdnum;
        double seconds = (sd - 1) * secondsPerSlot;
        int hh = (int) (seconds / 3600);
        int mm = (int) ((seconds % 3600) / 60);
        int ss = (int) (seconds % 60);
        return String.format("%02d:%02d:%02d", hh, mm, ss);
    }

    private static double thetFun(String date) {
        // 与 C++ 等价：thet_fun -> 基于年份日序计算角度（度）
        int year = getYear(date);
        double n0 = 79.6764 + 0.2422 * (year - 1985) - (int) ((year - 1985) / 4);
        double nt = dayOfYear(date) - n0;
        return 360.0 * nt / 365.2422;
    }

    private static double deltFun(double thetDeg) {
        double rad = Math.toRadians(thetDeg);
        double ssin = 23.2567 * Math.sin(rad) + 0.1149 * Math.sin(2 * rad) - 0.1712 * Math.sin(3 * rad);
        double scos = -0.7580 * Math.cos(rad) + 0.3656 * Math.cos(2 * rad) + 0.0201 * Math.cos(3 * rad);
        return 0.3723 + ssin + scos; // deg
    }

    private static double timeToSeconds(String time) {
        String[] p = time.split(":");
        int hh = Integer.parseInt(p[0]);
        int mm = Integer.parseInt(p[1]);
        int ss = p.length > 2 ? Integer.parseInt(p[2]) : 0;
        return hh * 3600.0 + mm * 60.0 + ss;
    }

    /**
     * 获取一年中的第几天
     */
    private static int dayOfYear(String date) {
        // 简化实现，假设日期格式为 "MM/dd/yyyy"
        try {
            String[] parts = date.split("/");
            int month = Integer.parseInt(parts[0]);
            int day = Integer.parseInt(parts[1]);
            int year = Integer.parseInt(parts[2]);
            
            int[] daysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
            if (isLeapYear(year)) {
                daysInMonth[1] = 29;
            }
            
            int dayOfYear = day;
            for (int i = 0; i < month - 1; i++) {
                dayOfYear += daysInMonth[i];
            }
            return dayOfYear;
        } catch (Exception e) {
            return 100; // 默认值
        }
    }

    private static int getYear(String date) {
        try {
            String[] parts = date.split("/");
            return Integer.parseInt(parts[2]);
        } catch (Exception e) {
            return 2020;
        }
    }

    private static Models.HisData defaultHis(String date, int sd) {
        Models.HisData d = new Models.HisData();
        d.Date = date;
        d.Sd = sd;
        d.MW0 = 0;
        d.Temp = 0;
        d.windSpeed = 0;
        d.windDir = 0;
        d.Pa = 0;
        d.Transp = 0;
        d.Humid = 0;
        d.Cloud = 0;
        d.Dni = 0;
        d.Descr = "";
        return d;
    }

    private static Models.ForData defaultFor(String date, int sd) {
        Models.ForData d = new Models.ForData();
        d.Date = date;
        d.Sd = sd;
        d.MW = 0;
        d.Temp = 0;
        d.windSpeed = 0;
        d.windDir = 0;
        d.Pa = 0;
        d.Transp = 0;
        d.Humid = 0;
        d.Cloud = 0;
        d.Dni = 0;
        d.Descr = "";
        return d;
    }

    private static boolean isLeapYear(int year) {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }

    /**
     * ANN配置类，包含神经网络参数
     */
    static class AnnConfig {
        public final int xnum;
        public final int hnum;
        public final int ynum;
        public final double pwrtol;
        public final double pv_nlat;
        public final List<String> infacts;
        
        // 神经网络权重矩阵（简化实现，使用随机初始化）
        public final double[][] W; // 输入层到隐藏层权重
        public final double[][] V; // 隐藏层到输出层权重
        public final double[] hth; // 隐藏层阈值
        public final double[] yth; // 输出层阈值

        public AnnConfig(Models.GlobalRc globalRc) {
            this.xnum = (int) globalRc.xnum;
            this.hnum = (int) globalRc.hnum;
            this.ynum = (int) globalRc.ynum;
            this.pwrtol = globalRc.pwrtol;
            this.pv_nlat = globalRc.pv_nlat;
            this.infacts = globalRc.Infacts;
            
            // 初始化权重矩阵（简化实现，使用随机值）
            this.W = new double[xnum + 1][hnum + 1];
            this.V = new double[hnum + 1][ynum + 1];
            this.hth = new double[hnum + 1];
            this.yth = new double[ynum + 1];
            
            // 随机初始化权重（对应C++中的ann_init()函数），使用 srand_num 可复现
            java.util.Random rnd = new java.util.Random(globalRc.srand_num > 0 ? globalRc.srand_num : System.currentTimeMillis());
            for (int i = 1; i <= xnum; i++) {
                for (int j = 1; j <= hnum; j++) {
                    W[i][j] = rnd.nextDouble() - 0.5; // [-0.5, 0.5)
                }
            }
            for (int i = 1; i <= hnum; i++) {
                for (int j = 1; j <= ynum; j++) {
                    V[i][j] = rnd.nextDouble() - 0.5;
                }
            }
            for (int i = 1; i <= hnum; i++) {
                hth[i] = (rnd.nextDouble() - 0.5) * 0.02; // [-0.01, 0.01)
            }
            for (int i = 1; i <= ynum; i++) {
                yth[i] = (rnd.nextDouble() - 0.5) * 0.02;
            }
        }
    }
}
